#include "sys_init.h"
#include "os/os.h"

#include "k_kit.h"

#include "FreeRTOS.h"
#include "task.h"

#undef SYS_LOG_DOMAIN
#define SYS_LOG_DOMAIN "OS"
#include "os_util.h"

#ifndef CONFIG_OS_THREAD_MAIN_STACK_SIZE
#define CONFIG_OS_THREAD_MAIN_STACK_SIZE 0x2000
#endif

#ifndef CONFIG_OS_THREAD_MAIN_PRIORITY
#define CONFIG_OS_THREAD_MAIN_PRIORITY OS_PRIORITY_NORMAL
#endif

#define _INIT_EXPORT_LEADER(NAME, LEVEL, PRIOR)                           \
    static int __sys_init_fn_##NAME(void)                                 \
    {                                                                     \
        return 0;                                                         \
    }                                                                     \
    __used __section(".s_sys_init_t." #LEVEL "." #PRIOR) sys_init_t const \
        __sys_init_##NAME = {                                             \
            .fn = __sys_init_fn_##NAME,                                   \
            .fn_name = #NAME,                                             \
            .line = __LINE__,                                             \
            .file = __FILE__,                                             \
            .level = LEVEL,                                               \
            .prior = PRIOR,                                               \
    }

#define _MODULE_EXPORT_LEADER(LEVEL)                            \
    __used __section(".sys_module_data." #LEVEL) module_t const \
        __sys_module_leader_##LEVEL = {                         \
            .name = "__sys_module_leader_" #LEVEL,              \
            .obj = NULL,                                        \
            .type = (module_type_t)~0,                          \
            .line = __LINE__,                                   \
            .file = __FILE__,                                   \
    }

_INIT_EXPORT_LEADER(leader_0, 0, 0);  // sys_init_t __sys_init_leader_0
_INIT_EXPORT_LEADER(leader_1, 1, 0);  // sys_init_t __sys_init_leader_1
_INIT_EXPORT_LEADER(leader_e, 9, 99); // sys_init_t __sys_init_leader_e

_MODULE_EXPORT_LEADER(0); // module_t __sys_module_leader_0
_MODULE_EXPORT_LEADER(9); // module_t __sys_module_leader_9

static void _sys_init(const sys_init_t *start, const sys_init_t *end)
{
    while (start < end)
    {
        start->fn();
        start = &start[1];
    }
}

__weak bool sys_log_enable_print(void)
{
    return true;
}

__weak int app_main(void)
{
    SYS_LOG_ERR("no app to run");
    return -1;
}

static void _work_app_main(void *arg)
{
    static uint8_t init_flag = 0;
    if (init_flag == 0)
    {
        init_flag = 1;
        _sys_init(&__sys_init_leader_1, &__sys_init_leader_e);
    }

    app_main();
}

/**
 * @brief  init the heap size
 * @param  heap
 * @param  size
 * @retval None
 */
static void prvInitialiseHeap(void *heap, size_t size)
{
    /* The Windows demo could create one large heap region, in which case it would
    be appropriate to use heap_4.  However, purely for demonstration purposes,
    heap_5 is used instead, so start by defining some heap regions.  No
    initialisation is required when any other heap implementation is used.  See
    http://www.freertos.org/a00111.html for more information.

    The xHeapRegions structure requires the regions to be defined in start address
    order, so this just creates one big array, then populates the structure with
    offsets into the array - with gaps in between and messy alignment just for test
    purposes. */
    const HeapRegion_t xHeapRegions[] = {
        /* Start address        Size */
        {heap, size},
        {NULL, 0}};

    vPortDefineHeapRegions(xHeapRegions);
}

static unsigned s_int_nest;

static unsigned _port_interrupt_save(void)
{
    if (s_int_nest == 0)
    {
        os_interrupt_disable();
    }
    return s_int_nest++;
}

static void _port_interrupt_restore(unsigned nest)
{
    s_int_nest = nest;
    if (nest == 0)
    {
        os_interrupt_enable();
    }
}

static k_work_q_t *_port_get_work_q_hdl(void)
{
    struct os_thread_handle *thread_handle = os_thread_get_self();
    if (thread_handle->work_q_list == NULL)
    {
        OS_ERR("curr_thread_handle is not work queue thread");
        return NULL;
    }
    os_work_q_list_t *work_q_list = thread_handle->work_q_list;
    return &work_q_list->work_q_handle;
}

static void _port_thread_sleep(k_tick_t sleep_ticks)
{
    vTaskDelay(sleep_ticks);
}

/**
 * @brief 指定堆内存并运行内核（包含 os_sched_init() 和 os_sched_start()）。
 * 内核运行后，首个任务为 int app_main(void)
 * 具体行为可查看相关代码
 *
 * @param heap 堆内存地址
 * @param size 堆内存大小（字节）
 * @return int -1
 */
int os_entry(void *heap, size_t size)
{
    prvInitialiseHeap(heap, size);

    _sys_init(&__sys_init_leader_0, &__sys_init_leader_1);

    static k_init_t const init_struct = {
        .malloc = os_malloc,
        .free = os_free,
        .get_sys_ticks = os_get_sys_ticks,
        .interrupt_save = _port_interrupt_save,
        .interrupt_restore = _port_interrupt_restore,
        .scheduler_disable = os_scheduler_suspend,
        .scheduler_enable = os_scheduler_resume,
        .get_work_q_hdl = _port_get_work_q_hdl,
        .thread_sleep = _port_thread_sleep,
    };
    k_init(&init_struct);

    os_work_q_create(default_os_work_q_hdl,
                     "app-work_q",
                     CONFIG_OS_THREAD_MAIN_STACK_SIZE,
                     CONFIG_OS_THREAD_MAIN_PRIORITY);

    static os_work_t _work_hdl_init;
    os_work_create(&_work_hdl_init, "work-main", _work_app_main, NULL, 3);
    os_work_submit(default_os_work_q_hdl, &_work_hdl_init, 0);

    vTaskStartScheduler();

    return -1;
}

/* 自动启动 shell ------------------------------------------------------------------ */

#if defined(MIX_SHELL)

#include "sh.h"
#include "board_config.h"

#if defined(MIX_DRV_LINUX)

static os_work_t s_work_handler_shell;

static void _isr_console(void)
{
    os_work_submit(default_os_work_q_hdl, &s_work_handler_shell, 0);
}

static void _work_sh(void *arg)
{
    char c;
    while (drv_uart_poll_read(g_board_uart_cons.id, &c) > 0)
    {
        sh_putc(&g_uart_handle_vt100, c);
    }
    os_work_later(10);
}

static int _create_shell_work(void)
{
    os_work_create(&s_work_handler_shell, "work-shell", _work_sh, NULL, 0);
    os_work_submit(default_os_work_q_hdl, &s_work_handler_shell, 0);

    drv_uart_irq_callback_enable(g_board_uart_cons.id, _isr_console);
    drv_uart_irq_enable(g_board_uart_cons.id, true, false, 15);
    return 0;
}
INIT_EXPORT_APP(_create_shell_work, 0);

#else

static os_work_t s_work_handler_shell;
static os_pipe_t s_pipe_handle_shell;

static void _isr_console(void)
{
    if (os_pipe_is_valid(&s_pipe_handle_shell))
    {
        char c;
        while (drv_uart_poll_read(g_board_uart_cons.id, &c) > 0)
        {
            os_pipe_poll_write(&s_pipe_handle_shell, c);
        }
    }
}

static void _work_sh(void *arg)
{
    uint8_t c;
    while (os_pipe_poll_read(&s_pipe_handle_shell, &c) > 0)
    {
        sh_putc(&g_uart_handle_vt100, c);
    }
}

static int _create_shell_work(void)
{
    os_work_create(&s_work_handler_shell, "work-shell", _work_sh, NULL, 0);
    os_work_submit(default_os_work_q_hdl, &s_work_handler_shell, 0);

    os_pipe_create(&s_pipe_handle_shell, 64);
    os_pipe_regist(&s_pipe_handle_shell, &s_work_handler_shell, 0);

    drv_uart_irq_callback_enable(g_board_uart_cons.id, _isr_console);
    drv_uart_irq_enable(g_board_uart_cons.id, true, false, 15);
    return 0;
}
INIT_EXPORT_APP(_create_shell_work, 0);

#endif

static int _show_shell_ver(void)
{
    sh_set_prompt(&g_uart_handle_vt100, "sh:/> ");
    sh_putstr_quiet(&g_uart_handle_vt100, "sh version");
    sh_putc(&g_uart_handle_vt100, '\r');
    return 0;
}
_INIT_EXPORT(_show_shell_ver, 9, 98);

#endif /* #if defined(MIX_SHELL) */
